package sudoku;

import java.util.HashSet;
import java.util.Random;

import evo.IInstance;

public class SudokuSolution implements IInstance {

	public SudokuProblem prob;
	public int[] board = new int[SudokuProblem.SIZE*SudokuProblem.SIZE];
	
	private static Random _r = new Random();
	
	public SudokuSolution(SudokuSolution sol) {
		this.prob = sol.prob;
		for (int i = 0; i < sol.board.length; i++) {
			this.board[i] = sol.board[i];
		}
	}
	
	public SudokuSolution(SudokuProblem prob) {
		this.prob = prob;
		for (int i = 0; i < SudokuProblem.SIZE*SudokuProblem.SIZE; i++) {
			this.board[i] = this.prob.board[i];
		}
	}
	
	@Override
	public double getFitness() {
		int conflicts = 0;
		for (int i = 0; i < SudokuProblem.SIZE; i++) {
			if (!this.checkCol(i))
				conflicts++;
			if (!this.checkRow(i))
				conflicts++;
			if (!this.checkSquare(i))
				conflicts++;
		}
		return SudokuProblem.SIZE*3 - conflicts;
	}

	@Override
	public IInstance mutate() {
		
		SudokuSolution newsol = new SudokuSolution(this);
		int i = 0;
		do
		{
			i = _r.nextInt(SudokuProblem.SIZE*SudokuProblem.SIZE);
		} while (newsol.board[i] != 0);
		
		newsol.board[i] = _r.nextInt(SudokuProblem.SIZE);
		return newsol;
	}

	@Override
	public IInstance crossover(IInstance other) {
		SudokuSolution child = new SudokuSolution(this.prob);
		for (int i = 0; i < SudokuProblem.SIZE*(SudokuProblem.SIZE/2); i++) {
			child.board[i] = this.board[i];
		}
		for (int i = SudokuProblem.SIZE*(SudokuProblem.SIZE/2); i < SudokuProblem.SIZE*SudokuProblem.SIZE; i++) {
			child.board[i] = ((SudokuSolution)other).board[i];
		}
		return child;
	}
	
	public HashSet<Integer> createSizeSet()
	{
		HashSet<Integer> s = new HashSet<Integer>();
		for (int i=1;i<= SudokuProblem.SIZE;i++)
			s.add(i);
		return s;
	}
	
	public boolean checkCol(int col)
	{
		HashSet<Integer> s = this.createSizeSet();
		for (int i=0;i<SudokuProblem.SIZE;i++) {
			int j = i*SudokuProblem.SIZE+col;
			if (s.contains(this.board[j]))
				s.remove(this.board[j]);
			else
				return false;
		}
		return s.isEmpty();
	}
	
	public boolean checkRow(int row)
	{
		HashSet<Integer> s = this.createSizeSet();
		for (int i=0;i<SudokuProblem.SIZE;i++) {
			int j = row*SudokuProblem.SIZE+i;
			if (s.contains(this.board[j]))
				s.remove(this.board[j]);
			else
				return false;
		}
		return s.isEmpty();
	}
	
	public boolean checkSquare(int q)
	{
		HashSet<Integer> s = this.createSizeSet();
		
		int r = (int)Math.sqrt(SudokuProblem.SIZE);
		int size = SudokuProblem.SIZE; 
		int pre = size*r*(q/r) + (q%r)*r;
		
		for (int i=0;i<SudokuProblem.SIZE;i++) {

			int j = pre + (i/r)*(size-r) + i;
			
			if (s.contains(this.board[j]))
				s.remove(this.board[j]);
			else
				return false;
		}
		return s.isEmpty();
	}
	
	public void randomize()
	{
		for (int i = 0; i < this.board.length; i++) {
			if (this.prob.board[i] == 0)
				this.board[i] = _r.nextInt(SudokuProblem.SIZE);
		}
	}

	@Override
	public double getMaxFitness() {
		return SudokuProblem.SIZE*3;
	}

	@Override
	public int compareTo(IInstance o) {
		return (int)Math.signum(this.getFitness() - o.getFitness());
	}
}
